## Generales
library(dplyr) # Limpieza, filtrado y modificación de datos
library(ggplot2) # Visualización de datos
library(stringr) # Operaciones básicas de limpieza de texto
library(lubridate) # Operaciones básicas para manejar variables en formato date
#Datos geograficos
library(sf) #Manipulacion de datos espaciales. Hay muchos, este es el mas recomendado
library(tmap) #visualización de datos espaciales. Same as above
# Text as adata
library(rtweet) # Funciones para conectarnos a la API de twitter
library(tm) # Funciones avanzadas de text-mining
library(SnowballC) # Funciones para encontrar la raíz de cada palabra
library(wordcloud) # Funciones para visualizar nubes de palabras
library(syuzhet) # Funciones para analisis de sentimiento
# Text as data/Networks
library(igraph)
library(ggraph)
Algunos argumentan que “todo es data”. Nosotros hacemos la salvedad de que para que algo sea considerado “data apta para el análisis estadístico” debe ser medible y cuatificable de forma estructurada.
Hay muchas cosas que cumplen estos requisitos.
Geografía: Los mapas y las cordenadas son data.
Texto: El texto es data.
Redes: Interacciones entre personas, empresas, países, etc… son “data”.
Multimedia: Las imágenes y el audio son data.
Audio is data. Científicos de la universidad de Harvard crean algoritmo de ML para adivinar cuales temas de Los Beatels escribió Paul McCartney. Source: “Lennon or McCartney? Machine learning tries to crack disputed Beatles authorship”, Financial Times
Las tecnologías digitales impulsan nuestra capacidad de generar, almancenar, y procesar datos.
Nuevas fuentes de datos nos brindan muchas posibilidades. Source: Rowe, F. 2021. Big Data and Human Geography. In: Demeritt, D. and Lees L. (eds) ConciseEncyclopedia of Human Geography. Edward Elgar Encyclopedias in the Social Sciences series.
Investigadores de múltiples disciplinas están usando algoriímos de IA para digitalizar textos antiguos (mucho mejor que escanear y transcribir). Source: Zejiang Shen, Kaixuan Zhang, Melissa Dell; Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR) Workshops, 2020, pp. 548-549
Muchos ejercicios de inferencia causal y de machine learning pueden llegar a mejor puerto con más información.
Nos permite medir fenómenos no medibles con la data convencional (i,e. trabajo de José Ramón sobre los apagones, usando luces nocturnas).
Nos permite explorar distintos mecanismos causales (i,e. Impacto de las telenovelas brasileñas sobre la fertilidad).
Nos permite cerrar puertas traseras via controles o variaciones exógenas (i,e. choques climáticos, datos gegráficos).
Nos permite crear modelos mas predicitvios (una de las aplicaciones de la data de iluminación nocturna es estimar el GDP de regiones desprovistos de estadísticas confiables).
En resumen, nos convierte en investigadores mas versátiles y creativos.
Hablémos brevemente del trabajo de Bazzi, Fisbein y Bebresilasse (2020).
Motivación: El historiador Frederick Turner escribió un ensayo que hablaba de cómo los habitantes de las comunidades fronterizas a lo interno de América del Norte desarrollaban eran mas individualistas y auto-eficasez, lo que terminaría moldeando la cultura del centro del país y lo diferenciaría del resto.
Preguntas: ¿Son distintas las personas que se mudan a zonas fronterizas? ¿Cúal es el efecto de vivir en la frontera? ¿Ese efecto es palpable hoy en día? - La responden con análisis de datos geográficos y de texto.
Bazzi, S., M. Fiszbein, & M. Gebresilasse [2020]; “Frontier Culture: The Roots and Persistence of ‘Rugged Individualism’ in The United States,” Econometrica.
Bazzi, S., M. Fiszbein, & M. Gebresilasse [2020]; “Frontier Culture: The Roots and Persistence of ‘Rugged Individualism’ in The United States,” Econometrica.
Variable dependiente: Qué tan poco frecuente es el nombre de los hijos nacidos en cada condado (mientras menos comúnes sean los nombres de los hijos, mas individualistas son los padres).
Resultado: Variable TFE tiene un impacto negativo, significativo, robusto a varias especificaciones, sobre la infrecuencia de los nombres de los hijos:
No sólo los hijos que tenían antes de migrar tenian nombres raros, sino que los nombres de los hijos que tuvieron despues de llegar a la frontera eran mas raros todavía. Estos hallazgos respaldan la existencia de dos mecanismos: uno de selección y otro de exposición.
Implicaciones: Entre los cándidatos presidenciales repúblicanos, sólo aquellos que que promovieron políticas más individualistas tuvieron éxito en esta región. Parece que esto ha condicionado
Bazzi, S., M. Fiszbein, & M. Gebresilasse [2020]; “Frontier Culture: The Roots and Persistence of ‘Rugged Individualism’ in The United States,” Econometrica.
Haremos un tour introductorio sobre algunos usos de los datos geográficos para producir evidencia.
Mencionaremos los principios de los datos geográficos (¿qué es un shapefile?, ¿qué es un Sistema de Coordenadas de Referencia (CRS)?)
Discutiremos las maneras en que se pueden visualizar estos datos (¿debería graficar mi data en puntos, poligonos, raster, o en redes y caminos? ¿y si la veo en 3d?)
Mencionaremos algunas maneras en que estos datos pueden aportar insumos para nuestro análisis cuantitativo.
Hay varios tipos de datos espaciales. Conocerlos y tener una noción de su estructura es clave para definir su uso. En resumen, hay tres tipos de objetos (puntos, líneas y áreas) y dos modelos de datos (vector y raster).
Source: Kolak ,M & Lin, Q. SER Workshop 2022 - “Intro to Spatial Analysis & GIS for Spatial Epidemiology in R”
Un simple CSV (o un .xslx) puede almacenar informacón de latitud y la longitud. Eso clasifica cómo datos geográficos.
Shapefiles: Es un formato de datos especial patentado por ESRI que recopila información de 4 archivos con extensiones diferentes (.shp, .shx, .dbf, and .prj)
GeoJSON: Un formato de datos abiertos lo suficientemente versátil como para adaptarse a varias estructuras de datos geográficos.
Los mapas son una proyección en dos dimensiones de datos tridimensionales. En el proceso de proyección se distorsiona el tamaño relativo de los puntos en el mapa (esta app ilustra muy bien lo que sucede). Cada método de proyección esta asociado a una CRS diferente.
Source: Kolak ,M & Lin, Q. SER Workshop 2022 - “Intro to Spatial Analysis & GIS for Spatial Epidemiology in R”
Replicaremos algunos elementos del Opioid Environment Toolkit, desarrollado por Healthy Regions & Policies Lab.
Imagina que estás en tu nueva pasantía en el ministerio de Salud de Chicago y te piden un análisis de la accesibilidad a Centros de Atención de Opiodes. Todo lo que tienes son los datos listados abajo y consola de Rstudio:
Clínicas de métadona de Chicago (methadone_clinics.shp): Ubicación de clíncas para dispensar medicamentos usados en el tratamiento de dependencia de opioides.
Códigos postales de Chicago (chicago_zips.shp): Polígonos delineando los límites de cada código postal en Chicago.
Límites de Chicago (boundaries_chicago.geojson): Polígonos delineando la frontera de Chicago.
Con el paquete sf es bastante sencillo.
metClinics <- st_read("data/opioids_toolkit_data/methadone_clinics.shp") # disclaimer: mira la definicion de los shapefile que pusimos arriba. Necesariamente deben haber 4 archivos con el mismo nombre pero difernente terminacion en esa carpeta
## Reading layer `methadone_clinics' from data source
## `C:\Users\cdabo\Dropbox\Causal Inference 2022\Documents\Workshop_01\data\opioids_toolkit_data\methadone_clinics.shp'
## using driver `ESRI Shapefile'
## Simple feature collection with 27 features and 8 fields
## Geometry type: POINT
## Dimension: XY
## Bounding box: xmin: -87.7349 ymin: 41.68698 xmax: -87.57673 ymax: 41.96475
## Geodetic CRS: WGS 84
chicagoZips <- st_read("data/opioids_toolkit_data/chicago_zips.shp")
## Reading layer `chicago_zips' from data source
## `C:\Users\cdabo\Dropbox\Causal Inference 2022\Documents\Workshop_01\data\opioids_toolkit_data\chicago_zips.shp'
## using driver `ESRI Shapefile'
## Simple feature collection with 85 features and 9 fields
## Geometry type: MULTIPOLYGON
## Dimension: XY
## Bounding box: xmin: -88.06058 ymin: 41.58152 xmax: -87.52366 ymax: 42.06504
## Geodetic CRS: WGS 84
cityBoundary <- st_read("data/opioids_toolkit_data/boundaries_chicago.geojson")
## Reading layer `boundaries_chicago' from data source
## `C:\Users\cdabo\Dropbox\Causal Inference 2022\Documents\Workshop_01\data\opioids_toolkit_data\boundaries_chicago.geojson'
## using driver `GeoJSON'
## Simple feature collection with 1 feature and 4 fields
## Geometry type: MULTIPOLYGON
## Dimension: XY
## Bounding box: xmin: -87.94011 ymin: 41.64454 xmax: -87.52414 ymax: 42.02304
## Geodetic CRS: WGS 84
tmap_mode("plot") #esta funcion garantiza que tmap solo produzca graficos estaticos (son mas livianos)
#Tmap funciona casi igual que ggplot. Cada detalle se va añadiendo como capas adicionales despues de un simbolo +
## 1era capa: Poligonos, areas
tm_shape(chicagoZips) +
tm_borders(alpha = 0.4) +
## 2da capa: Puntos
tm_shape(metClinics) +
tm_dots(size = 0.4, col="red") +
tm_layout(title="¿Cómo saber si está bien?")
## Revisemos si ambos archivos shape tienen las mismas CRS
identical(st_crs(metClinics),st_crs(chicagoZips))
## [1] TRUE
## Estaba bien en el sentido de que estaban en el mismo CRS, pero hay una mejor alternativa.
## Usemos un CRS util para nuestro caso: Queremos uno que preserve las distancias. Buscamos en google "CRS Illinios" y encontramos el CRS EPSG:1748, la mas precisa para medir distancias en el estado de Illinois.
CRS.new <- st_crs("EPSG:3435")
metClinics.3435 <- st_transform(metClinics, CRS.new)
chicagoZips.3435 <- st_transform(chicagoZips, CRS.new)
st_crs(metClinics.3435)
## Coordinate Reference System:
## User input: EPSG:3435
## wkt:
## PROJCRS["NAD83 / Illinois East (ftUS)",
## BASEGEOGCRS["NAD83",
## DATUM["North American Datum 1983",
## ELLIPSOID["GRS 1980",6378137,298.257222101,
## LENGTHUNIT["metre",1]]],
## PRIMEM["Greenwich",0,
## ANGLEUNIT["degree",0.0174532925199433]],
## ID["EPSG",4269]],
## CONVERSION["SPCS83 Illinois East zone (US Survey feet)",
## METHOD["Transverse Mercator",
## ID["EPSG",9807]],
## PARAMETER["Latitude of natural origin",36.6666666666667,
## ANGLEUNIT["degree",0.0174532925199433],
## ID["EPSG",8801]],
## PARAMETER["Longitude of natural origin",-88.3333333333333,
## ANGLEUNIT["degree",0.0174532925199433],
## ID["EPSG",8802]],
## PARAMETER["Scale factor at natural origin",0.999975,
## SCALEUNIT["unity",1],
## ID["EPSG",8805]],
## PARAMETER["False easting",984250,
## LENGTHUNIT["US survey foot",0.304800609601219],
## ID["EPSG",8806]],
## PARAMETER["False northing",0,
## LENGTHUNIT["US survey foot",0.304800609601219],
## ID["EPSG",8807]]],
## CS[Cartesian,2],
## AXIS["easting (X)",east,
## ORDER[1],
## LENGTHUNIT["US survey foot",0.304800609601219]],
## AXIS["northing (Y)",north,
## ORDER[2],
## LENGTHUNIT["US survey foot",0.304800609601219]],
## USAGE[
## SCOPE["Engineering survey, topographic mapping."],
## AREA["United States (USA) - Illinois - counties of Boone; Champaign; Clark; Clay; Coles; Cook; Crawford; Cumberland; De Kalb; De Witt; Douglas; Du Page; Edgar; Edwards; Effingham; Fayette; Ford; Franklin; Gallatin; Grundy; Hamilton; Hardin; Iroquois; Jasper; Jefferson; Johnson; Kane; Kankakee; Kendall; La Salle; Lake; Lawrence; Livingston; Macon; Marion; Massac; McHenry; McLean; Moultrie; Piatt; Pope; Richland; Saline; Shelby; Vermilion; Wabash; Wayne; White; Will; Williamson."],
## BBOX[37.06,-89.28,42.5,-87.02]],
## ID["EPSG",3435]]
Dibujaremos una circunferencia de 1 milla de radio al rededor de cada clínica.
# 1 mella son 5280 pies
metClinic_buffers <- st_buffer(metClinics.3435, dist = 5280)
# Primer intento
tm_shape(chicagoZips.3435) + tm_borders() +
tm_shape(metClinics.3435) + tm_dots(col = "red") +
# añadimos los buffers (circunferencias)
tm_shape(metClinic_buffers) + tm_borders(col = "blue")
# Segundo intento
tm_shape(chicagoZips) + tm_borders(alpha = 0.6) +
# ponemos los buffers antes de los bordes y los puntos, y los rellenamos
tm_shape(metClinic_buffers) + tm_fill(col = "blue", alpha = .4) +
tm_borders(col = "blue") +
tm_shape(metClinics.3435) + tm_dots(col = "red", size = 0.2)
Hay varias circunferencias que se superponen, unámoslas en áreas comunes de acceso immediato
# Union: junto todas las circunferencias que se superpongan. Tambien hay intersección (util para identificar areas cubiertas por mas de una clinica)
unionBuffers <- st_union(metClinic_buffers)
tm_shape(chicagoZips) + tm_borders()+
# No añades el shape de las circunferencias, sino de las nuevas areas comunes
tm_shape(unionBuffers) + tm_fill(col = "blue", alpha = .2) +
tm_borders(col = "blue") +
tm_shape(metClinics.3435) + tm_dots(col = "red", size = 0.4)
Te felicitan por delinear las áreas comunes de atención immediata, pero pero al final de la reunión te preguntan qué tan lejos está el centro la clínica mas cercana para los habitantes del código postal 22145.
No tenemos la ubicación del habitante promedio del código postal 22145, pero, haciendo algunas suposiciones, podemos aproximarnos a ella y calcular la distancia entre esta ubicación y el centro de atención mas cercano. Usaremos el centroide de cada código postal cómo nuestra estimación.
chicagoCentroids <- st_centroid(chicagoZips) # esta funcion nos permite obtener los centroides
chicagoCentroids.3435 <- st_transform(chicagoCentroids, CRS.new)
#vistazo a la data
chicagoCentroids
## Simple feature collection with 85 features and 9 fields
## Geometry type: POINT
## Dimension: XY
## Bounding box: xmin: -87.99311 ymin: 41.61322 xmax: -87.55133 ymax: 42.03526
## Geodetic CRS: WGS 84
## First 10 features:
## ZCTA5CE10 GEOID10 CLASSFP10 MTFCC10 FUNCSTAT10 ALAND10 AWATER10 INTPTLAT10
## 1 60501 60501 B5 G6350 S 12532295 974360 +41.7802209
## 2 60007 60007 B5 G6350 S 36493383 917560 +42.0086000
## 3 60651 60651 B5 G6350 S 9052862 0 +41.9020934
## 4 60652 60652 B5 G6350 S 12987857 0 +41.7479319
## 5 60653 60653 B5 G6350 S 6041418 1696670 +41.8199645
## 6 60654 60654 B5 G6350 S 1464813 113471 +41.8918225
## 7 60655 60655 B5 G6350 S 11408010 0 +41.6947762
## 8 60656 60656 B5 G6350 S 8465226 0 +41.9742800
## 9 60657 60657 B5 G6350 S 5888324 2025836 +41.9402931
## 10 60659 60659 B5 G6350 S 5251086 2818 +41.9914885
## INTPTLON10 geometry
## 1 -087.8232440 POINT (-87.82396 41.78014)
## 2 -087.9973398 POINT (-87.99311 42.00759)
## 3 -087.7408565 POINT (-87.74086 41.90209)
## 4 -087.7147951 POINT (-87.7148 41.74793)
## 5 -087.6059654 POINT (-87.60604 41.81987)
## 6 -087.6383036 POINT (-87.63726 41.89228)
## 7 -087.7037764 POINT (-87.70378 41.69478)
## 8 -087.8271283 POINT (-87.82713 41.97428)
## 9 -087.6468569 POINT (-87.64926 41.94033)
## 10 -087.7039859 POINT (-87.70406 41.99115)
#vistazo a la data (grafico)
tm_shape(chicagoZips) +
tm_borders() +
tm_shape(chicagoCentroids.3435) +
tm_dots()+
tm_shape(metClinics.3435) +
tm_dots(col = "red", size = 0.4)
## Esta funcion calcula la distancia e identifica los pares mas cercanos
## Veamos que hay en ?st_nearest_feature()
nearestClinic_indexes <- st_nearest_feature(x= chicagoCentroids.3435,
y = metClinics.3435)
# nearestClinic_indexes es un vector Clinica mas cercano para cada zip code de chicago
# De todas las clinicas, conservamos la que aparecen en la lista de mas cercanas a algún codigo postal
nearestClinic <- metClinics.3435[nearestClinic_indexes,]
nearestClinic # la clinica mas cercana a cada codigo postal
## Simple feature collection with 85 features and 8 fields
## Geometry type: POINT
## Dimension: XY
## Bounding box: xmin: 1147259 ymin: 1829330 xmax: 1190680 ymax: 1930471
## Projected CRS: NAD83 / Illinois East (ftUS)
## First 10 features:
## X Name
## 16 16 Katherine Boone Robinson Foundation
## 7 7 A Rincon Family Services
## 7.1 7 A Rincon Family Services
## 26 26 New Hope Community Service Center
## 15 15 HRDI- Grand Boulevard Professional Counseling Center
## 5 5 Center for Addictive Problems, Inc.
## 26.1 26 New Hope Community Service Center
## 7.2 7 A Rincon Family Services
## 1 1 Chicago Treatment and Counseling Center, Inc.
## 3 3 Soft Landing Interventions/DBA Symetria Recovery of Lakeview
## Address City State Zip
## 16 4100 W. Ogden Ave. Chicago IL 60623
## 7 3809 W. Grand Ave. Chicago IL 60651
## 7.1 3809 W. Grand Ave. Chicago IL 60651
## 26 2559 W. 79th St. Chicago IL 60652
## 15 340 E. 51st St. Chicago IL 60615
## 5 609 N. Wells St. Chicago IL 60654
## 26.1 2559 W. 79th St. Chicago IL 60652
## 7.2 3809 W. Grand Ave. Chicago IL 60651
## 1 4453 North Broadway st. Chicago IL 60640
## 3 3934 N. Lincoln Ave. Chicago IL 60613
## fullAdd geo_method
## 16 4100 W. Ogden Ave. Chicago IL 60623 census
## 7 3809 W. Grand Ave. Chicago IL 60651 census
## 7.1 3809 W. Grand Ave. Chicago IL 60651 census
## 26 2559 W. 79th St. Chicago IL 60652 osm
## 15 340 E. 51st St. Chicago IL 60615 census
## 5 609 N. Wells St. Chicago IL 60654 census
## 26.1 2559 W. 79th St. Chicago IL 60652 osm
## 7.2 3809 W. Grand Ave. Chicago IL 60651 census
## 1 4453 North Broadway st. Chicago IL 60640 osm
## 3 3934 N. Lincoln Ave. Chicago IL 60613 census
## geometry
## 16 POINT (1149437 1888620)
## 7 POINT (1150707 1908328)
## 7.1 POINT (1150707 1908328)
## 26 POINT (1160422 1852071)
## 15 POINT (1179319 1871281)
## 5 POINT (1174632 1904257)
## 26.1 POINT (1160422 1852071)
## 7.2 POINT (1150707 1908328)
## 1 POINT (1168556 1929911)
## 3 POINT (1162460 1926257)
## Esta funcion nos permite ir un paso atras y recalcular la distancia entre ambos sets de puntos (los zip codes y sus clinicas mas cercanas)
min_dist<-st_distance(x = chicagoCentroids.3435,
y = nearestClinic, by_element = TRUE)
min_dist_mi <- min_dist/5280
hist(min_dist_mi)
# Lo bueno de los objetos sf es que les puedes añadir columnas y modificar atributos cómo si de un data.frame se tratase
min_dist_mi <- cbind(chicagoZips, min_dist_mi)
min_dist_mi
## Simple feature collection with 85 features and 10 fields
## Geometry type: MULTIPOLYGON
## Dimension: XY
## Bounding box: xmin: -88.06058 ymin: 41.58152 xmax: -87.52366 ymax: 42.06504
## Geodetic CRS: WGS 84
## First 10 features:
## ZCTA5CE10 GEOID10 CLASSFP10 MTFCC10 FUNCSTAT10 ALAND10 AWATER10 INTPTLAT10
## 1 60501 60501 B5 G6350 S 12532295 974360 +41.7802209
## 2 60007 60007 B5 G6350 S 36493383 917560 +42.0086000
## 3 60651 60651 B5 G6350 S 9052862 0 +41.9020934
## 4 60652 60652 B5 G6350 S 12987857 0 +41.7479319
## 5 60653 60653 B5 G6350 S 6041418 1696670 +41.8199645
## 6 60654 60654 B5 G6350 S 1464813 113471 +41.8918225
## 7 60655 60655 B5 G6350 S 11408010 0 +41.6947762
## 8 60656 60656 B5 G6350 S 8465226 0 +41.9742800
## 9 60657 60657 B5 G6350 S 5888324 2025836 +41.9402931
## 10 60659 60659 B5 G6350 S 5251086 2818 +41.9914885
## INTPTLON10 min_dist_mi geometry
## 1 -087.8232440 6.962975 [US_survey_foot] MULTIPOLYGON (((-87.86289 4...
## 2 -087.9973398 15.685945 [US_survey_foot] MULTIPOLYGON (((-88.06058 4...
## 3 -087.7408565 0.992006 [US_survey_foot] MULTIPOLYGON (((-87.77559 4...
## 4 -087.7147951 1.405126 [US_survey_foot] MULTIPOLYGON (((-87.74205 4...
## 5 -087.6059654 1.371441 [US_survey_foot] MULTIPOLYGON (((-87.62623 4...
## 6 -087.6383036 0.165529 [US_survey_foot] MULTIPOLYGON (((-87.64775 4...
## 7 -087.7037764 3.885578 [US_survey_foot] MULTIPOLYGON (((-87.73973 4...
## 8 -087.8271283 7.260946 [US_survey_foot] MULTIPOLYGON (((-87.86957 4...
## 9 -087.6468569 1.612944 [US_survey_foot] MULTIPOLYGON (((-87.6785 41...
## 10 -087.7039859 2.931816 [US_survey_foot] MULTIPOLYGON (((-87.7289 41...
tm_shape(min_dist_mi) +
tm_polygons("min_dist_mi", style = 'quantile', n=5,
title = "Minimum Distance (mi)") +
tm_layout(main.title = "Minimum Distance from Zip Centroid\n to Methadone Clinic",
main.title.position = "center",
main.title.size = 1)
Tu jefe te interrumpe mientras preparabas tu nuevo informe. Debido al repunte de casos de Covid-19, es urgente que añadas a tu informe cuales son las Clínicas de Métadonas con menos riesgo de contagio.
La cantidad de casos por cada 100 mil habitantes está en la data “COVID-
19_Cases__Tests__and_Deaths_by_ZIP_Code.csv” (disponible en el Portal de Datos de Chicago)
COVID <- read.csv("data/opioids_toolkit_data/COVID-19_Cases__Tests__and_Deaths_by_ZIP_Code.csv")
## Primero inspeccionemos el dataset de COVID
head(COVID)
## ZIP.Code Week.Number Week.Start Week.End Cases...Weekly Cases...Cumulative
## 1 60602 44 10/25/2020 10/31/2020 3 35
## 2 60604 51 12/13/2020 12/19/2020 0 74
## 3 60604 52 12/20/2020 12/26/2020 2 76
## 4 60604 53 12/27/2020 01/02/2021 1 77
## 5 60604 24 06/07/2020 06/13/2020 1 22
## 6 60604 25 06/14/2020 06/20/2020 0 22
## Case.Rate...Weekly Case.Rate...Cumulative Tests...Weekly Tests...Cumulative
## 1 241 2813.5 65 976
## 2 0 9462.9 75 1073
## 3 256 9718.7 49 1122
## 4 128 9846.5 54 1176
## 5 128 2813.3 9 134
## 6 0 2813.3 16 150
## Test.Rate...Weekly Test.Rate...Cumulative Percent.Tested.Positive...Weekly
## 1 5225 78456.6 0.0
## 2 9591 137212.3 0.0
## 3 6266 143478.3 0.1
## 4 6905 150383.6 0.0
## 5 1151 17135.5 0.1
## 6 2046 19181.6 0.0
## Percent.Tested.Positive...Cumulative Deaths...Weekly Deaths...Cumulative
## 1 0.0 0 0
## 2 0.1 0 0
## 3 0.1 0 0
## 4 0.1 0 0
## 5 0.2 0 0
## 6 0.2 0 0
## Death.Rate...Weekly Death.Rate...Cumulative Population Row.ID
## 1 0 0 1244 60602-2020-44
## 2 0 0 782 60604-2020-51
## 3 0 0 782 60604-2020-52
## 4 0 0 782 60604-2020-53
## 5 0 0 782 60604-2020-24
## 6 0 0 782 60604-2020-25
## ZIP.Code.Location
## 1 POINT (-87.628309 41.883136)
## 2 POINT (-87.629029 41.878153)
## 3 POINT (-87.629029 41.878153)
## 4 POINT (-87.629029 41.878153)
## 5 POINT (-87.629029 41.878153)
## 6 POINT (-87.629029 41.878153)
names(COVID)
## [1] "ZIP.Code"
## [2] "Week.Number"
## [3] "Week.Start"
## [4] "Week.End"
## [5] "Cases...Weekly"
## [6] "Cases...Cumulative"
## [7] "Case.Rate...Weekly"
## [8] "Case.Rate...Cumulative"
## [9] "Tests...Weekly"
## [10] "Tests...Cumulative"
## [11] "Test.Rate...Weekly"
## [12] "Test.Rate...Cumulative"
## [13] "Percent.Tested.Positive...Weekly"
## [14] "Percent.Tested.Positive...Cumulative"
## [15] "Deaths...Weekly"
## [16] "Deaths...Cumulative"
## [17] "Death.Rate...Weekly"
## [18] "Death.Rate...Cumulative"
## [19] "Population"
## [20] "Row.ID"
## [21] "ZIP.Code.Location"
## Tiene nombres muy raros, usemos la funcion clean_names() del paquete janitor
COVID<-janitor::clean_names(COVID)
names(COVID)
## [1] "zip_code" "week_number"
## [3] "week_start" "week_end"
## [5] "cases_weekly" "cases_cumulative"
## [7] "case_rate_weekly" "case_rate_cumulative"
## [9] "tests_weekly" "tests_cumulative"
## [11] "test_rate_weekly" "test_rate_cumulative"
## [13] "percent_tested_positive_weekly" "percent_tested_positive_cumulative"
## [15] "deaths_weekly" "deaths_cumulative"
## [17] "death_rate_weekly" "death_rate_cumulative"
## [19] "population" "row_id"
## [21] "zip_code_location"
## Cuantas observaciones tiene? Hmmmm Parece que es un panel de datos. Conservemos el último período
nrow(COVID)
## [1] 7380
COVID$week_end<-str_replace_all(COVID$week_end,"/","-")
COVID$week_end<-lubridate::mdy(COVID$week_end)
COVID<-filter(COVID, week_end==max(week_end)) %>%
select(zip_code,week_end,case_rate_cumulative, death_rate_cumulative)
## Juntemos la base de datos con los zip codes de Chicago. El formato SF hace que sea tan sencillo como juntar dos data.frames
chicagoZips_covid<-chicagoZips %>%
## A todos los zip codes de chicago le anexo su tasa acumulada de casos en la ultima semana de 2020
inner_join(COVID, by=c("GEOID10"="zip_code"))
## Grafico heatmap con tasa de contagios.
tm_shape(chicagoZips_covid) +
tm_fill("case_rate_cumulative", style="quantile", pal="-RdYlGn",
title = "COVID Case Rate") +
tm_shape(unionBuffers) + tm_fill(alpha=0.5) +
tm_borders(col = "grey") +
tm_shape(metClinics.3435) +
tm_dots(col = "black", size = 0.1) +
tm_layout(main.title="Clínicas de Metadona seguras",
main.title.position = "center",
main.title.size = 1,
frame = FALSE)
El formato raster es distinto al formato shape (o vectores). Un archivo raster es una gran matriz donde cada pixel o grilla representa un area geográfica determinada. Heremos un breve ejercicio con los datos sobre la composición de la superficie de la tierra, que vienen incluídos en el paquete tm_map.
data(World, land)
## Inspeccionemos land y world
land$cover[1:5,1:5]
## [,1] [,2] [,3] [,4] [,5]
## [1,] Water bodies Water bodies Water bodies Water bodies Water bodies
## [2,] Water bodies Water bodies Water bodies Water bodies Water bodies
## [3,] Water bodies Water bodies Water bodies Water bodies Water bodies
## [4,] Water bodies Water bodies Water bodies Water bodies Water bodies
## [5,] Water bodies Water bodies Water bodies Water bodies Water bodies
## 20 Levels: Broadleaf Evergreen Forest ... Water bodies
table(land$cover) # uno de sus componenetes es una matriz donde cada celda muestra su tipo de superficie
##
## Broadleaf Evergreen Forest Broadleaf Deciduous Forest
## 9140 6660
## Needleleaf Evergreen Forest Needleleaf Deciduous Forest
## 6124 6622
## Mixed Forest Tree Open
## 4134 16171
## Shrub Herbaceous
## 9341 21377
## Herbaceous with Sparse Tree/Shrub Sparse vegetation
## 1893 12247
## Cropland Paddy field
## 11658 598
## Cropland / Other Vegetation Mosaic Mangrove
## 5587 65
## Wetland Bare area,consolidated (gravel,rock)
## 1492 7436
## Bare area,unconsolidated (sand) Urban
## 7221 388
## Snow / Ice Water bodies
## 61986 393060
land$trees[500:505,100:105] # Tambien tiene otra matriz con el % de la celda cubierto por arboles
## [,1] [,2] [,3] [,4] [,5] [,6]
## [1,] NA NA NA NA NA NA
## [2,] NA NA NA NA NA NA
## [3,] NA NA NA NA NA NA
## [4,] NA NA NA NA NA NA
## [5,] NA NA NA NA NA NA
## [6,] NA NA NA NA NA NA
table(land$trees, useNA = "always")
##
## 0 1 2 3 4 5 6 7 8 9 10
## 106841 3899 3374 3067 2926 2357 2310 1926 1898 1666 1631
## 11 12 13 14 15 16 17 18 19 20 21
## 1413 1401 1272 1262 1124 1168 1031 1094 985 1030 984
## 22 23 24 25 26 27 28 29 30 31 32
## 930 879 925 815 893 772 774 715 782 726 747
## 33 34 35 36 37 38 39 40 41 42 43
## 594 709 615 690 588 653 612 601 554 600 556
## 44 45 46 47 48 49 50 51 52 53 54
## 555 536 573 510 560 505 588 519 576 501 484
## 55 56 57 58 59 60 61 62 63 64 65
## 495 539 508 540 505 536 486 525 511 497 480
## 66 67 68 69 70 71 72 73 74 75 76
## 473 496 518 510 541 460 512 486 531 520 540
## 77 78 79 80 81 82 83 84 85 86 87
## 480 544 501 473 522 505 498 506 468 494 524
## 88 89 90 91 92 93 94 95 96 97 98
## 508 470 511 460 431 516 443 398 424 367 292
## 99 100 <NA>
## 298 2002 393060
land$elevation[100:104,100:105] # Por ultimo esta la elevacion de cada celda respecto al nivel del mar
## [,1] [,2] [,3] [,4] [,5] [,6]
## [1,] NA NA NA NA NA NA
## [2,] NA NA NA NA NA NA
## [3,] NA NA NA NA NA NA
## [4,] NA NA NA NA NA NA
## [5,] NA NA NA NA NA NA
## Visualicemos la densidad de los arboles plantados
tm_shape(land, ylim = c(-88,88)) +
## esta es la funcion para graficar los arboles
tm_raster("trees", title = "Densidad de arboles plantados",palette = "Greens")+
## Poligonos de los continetnes y paises
tm_shape(World) +
## Detalles esteticos
tm_borders(col = "black") +
tm_layout(scale = .8,
legend.position = c("left","bottom"),
legend.bg.color = "white", legend.bg.alpha = .2,
legend.frame = "gray50")
## Primero debemos asegurarnos que LAND tenga las mismas coordenadas de World
CRS.new <- sf::st_crs(land)
World_new<-st_transform(World, CRS.new)
st_crs(World_new)
## Coordinate Reference System:
## User input: +proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs
## wkt:
## BOUNDCRS[
## SOURCECRS[
## GEOGCRS["unknown",
## DATUM["Unknown based on WGS84 ellipsoid",
## ELLIPSOID["WGS 84",6378137,298.257223563,
## LENGTHUNIT["metre",1],
## ID["EPSG",7030]]],
## PRIMEM["Greenwich",0,
## ANGLEUNIT["degree",0.0174532925199433],
## ID["EPSG",8901]],
## CS[ellipsoidal,2],
## AXIS["longitude",east,
## ORDER[1],
## ANGLEUNIT["degree",0.0174532925199433,
## ID["EPSG",9122]]],
## AXIS["latitude",north,
## ORDER[2],
## ANGLEUNIT["degree",0.0174532925199433,
## ID["EPSG",9122]]]]],
## TARGETCRS[
## GEOGCRS["WGS 84",
## DATUM["World Geodetic System 1984",
## ELLIPSOID["WGS 84",6378137,298.257223563,
## LENGTHUNIT["metre",1]]],
## PRIMEM["Greenwich",0,
## ANGLEUNIT["degree",0.0174532925199433]],
## CS[ellipsoidal,2],
## AXIS["latitude",north,
## ORDER[1],
## ANGLEUNIT["degree",0.0174532925199433]],
## AXIS["longitude",east,
## ORDER[2],
## ANGLEUNIT["degree",0.0174532925199433]],
## ID["EPSG",4326]]],
## ABRIDGEDTRANSFORMATION["Transformation from unknown to WGS84",
## METHOD["Position Vector transformation (geog2D domain)",
## ID["EPSG",9606]],
## PARAMETER["X-axis translation",0,
## ID["EPSG",8605]],
## PARAMETER["Y-axis translation",0,
## ID["EPSG",8606]],
## PARAMETER["Z-axis translation",0,
## ID["EPSG",8607]],
## PARAMETER["X-axis rotation",0,
## ID["EPSG",8608]],
## PARAMETER["Y-axis rotation",0,
## ID["EPSG",8609]],
## PARAMETER["Z-axis rotation",0,
## ID["EPSG",8610]],
## PARAMETER["Scale difference",1,
## ID["EPSG",8611]]]]
## Usamos la funcion aggregate del paquete sf que projectara las celdas del raster en los poligonos de World (Puede tomar de 20 segundos a varios minutos dependiendo de tu PC)
land_country <- aggregate(land, World_new, mean, na.rm=T)
nrow(land_country)
## geometry
## 177
nrow(World_new)
## [1] 177
World_trees <- mutate(World_new, trees = land_country$trees)
## Visualizémoslo en un ranking con ggplot, organizado por tipo de economia
World_trees %>%
# convierto el sf object en un data.frame. En terminos practicos no cambia nada, pero es mas "limpio"
as.data.frame()%>%
# Conservo las variables que quiero usar
select(name, trees,continent) %>%
# No toods los continentes tienen arboles. Alquien me explica como funciona este comando?
filter(! continent %in% c("Seven seas (open ocean)")) %>%
filter(! name %in% c("Antarctica")) %>%
# Agrupo por continente y preservo los top x valores
group_by(continent) %>%
top_n(6, trees) %>%
# Inicio ggplot.
ggplot(aes(x=reorder(name,trees), y=trees))+
geom_col()+
coord_flip()+
facet_wrap(.~str_wrap(continent,50), ncol = 3,scales = "free")+
labs(y="% de superficie con Árboles",
x=NULL)
## Visualizémoslo en el mapa
tm_shape(World_trees)+
tm_polygons("trees", style = 'quantile', n=10,palette="Greens",
title = "% de superficie con Árboles") +
tm_borders(col = "black")+
tm_layout(frame = FALSE)
Un gran porcentaje de los datos producidos por el hombre está en formato de texto. Esta introducción tendrá un formato similar al anterior:
Mencionaremos los principios de los datos de texto (¿Cómo leer esos datos en R?, ¿Para que se usan?)
Discutiremos maneras de analizarlos (¿Cómo manejo inconsistencias y errores ortográficos?, ¿Cómo los vizualizo?)
Cómo producir insumos cuantitativos a partir de estos (¿que es el análisis de sentimiento?).
Haremos un ejercicio de análisis texto con twitter.
Referencias:
Cómo acceser a los datos de Twitter (link)
Todos pueden pedir permiso para crear una aplicación (acceder a la API) en este link.
Podemos hacer analisis de sentimiento sobre los tweets (link)
Referencia estrella: Un buen ejemplo de lo que pueden llegar a construir https://johncoene.shinyapps.io/chirp_demo/
# Twitter app: CausalDaboin
# API KEY: qRjiyqFdYW5ezrubjxaJrJTIs
# API KEY SECREt: RrJHuTF9XxL8xfj6JWvsTWQcKwNxKIbEOTcin6COvvvp5UMBKN
# Bearer token: AAAAAAAAAAAAAAAAAAAAALGVegEAAAAA0F1X2qZglMkNDWPeKjffB3dKLdo%3DPUpmJwvjGPBkGoS5Pq4wxNTtJDBzmj6bBXkS11qDwzAIiBum5m
# Access token secret: M0r9wA2Re0GVfrkFLwBki8iWyQ3IujwEj7IvLIr9zU6yW
# Access token: 599482460-SKifuC3KQOUb04hMS4JOY1iLCJaEKG9X2ehWwXEx
appname<-"CausalDaboin"
key<-"qRjiyqFdYW5ezrubjxaJrJTIs"
secret<-"RrJHuTF9XxL8xfj6JWvsTWQcKwNxKIbEOTcin6COvvvp5UMBKN"
twitter_token<-create_token(app=appname,
consumer_key = key,
consumer_secret = secret,
access_token = "599482460-SKifuC3KQOUb04hMS4JOY1iLCJaEKG9X2ehWwXEx",
access_secret = "M0r9wA2Re0GVfrkFLwBki8iWyQ3IujwEj7IvLIr9zU6yW")
twitter_token
## <Token>
## <oauth_endpoint>
## request: https://api.twitter.com/oauth/request_token
## authorize: https://api.twitter.com/oauth/authenticate
## access: https://api.twitter.com/oauth/access_token
## <oauth_app> CausalDaboin
## key: qRjiyqFdYW5ezrubjxaJrJTIs
## secret: <hidden>
## <credentials> oauth_token, oauth_token_secret
## ---
load("data/all_twitter_text_data.RData")
## Extraígamos los timelines de dos cuentas
# timelines<-get_timeline(user = c("enlaucab","Unimet","usm_vzla"), n=500)
## Revisemos el data.set que nos devuelve
head(timelines)
## # A tibble: 6 x 90
## user_id status_id created_at screen_name text source
## <chr> <chr> <dttm> <chr> <chr> <chr>
## 1 50366308 1545920778394370049 2022-07-10 00:00:10 enlaucab "José Hum~ Hoots~
## 2 50366308 1545890579183243272 2022-07-09 22:00:09 enlaucab "En Carac~ Hoots~
## 3 50366308 1545860391888093187 2022-07-09 20:00:12 enlaucab "Hasta el~ Hoots~
## 4 50366308 1545830200985935873 2022-07-09 18:00:14 enlaucab "Con gala~ Hoots~
## 5 50366308 1545813681992335360 2022-07-09 16:54:36 enlaucab "Ya está ~ Twitt~
## 6 50366308 1545800117726461953 2022-07-09 16:00:42 enlaucab "Ucabista~ Hoots~
## # ... with 84 more variables: display_text_width <dbl>,
## # reply_to_status_id <chr>, reply_to_user_id <chr>,
## # reply_to_screen_name <chr>, is_quote <lgl>, is_retweet <lgl>,
## # favorite_count <int>, retweet_count <int>, quote_count <int>,
## # reply_count <int>, hashtags <list>, symbols <list>, urls_url <list>,
## # urls_t.co <list>, urls_expanded_url <list>, media_url <list>,
## # media_t.co <list>, media_expanded_url <list>, media_type <list>, ...
## vemaos cual tiene mas segudores, cual tiene mas likes, y cual tiene mas retweets
timelines %>%
# screen name es el nombre de la cuenta
group_by(screen_name) %>%
summarise(avg_likes=mean(favorite_count,na.rm=T),
avg_retweets=mean(retweet_count, na.rm=T))
## # A tibble: 3 x 3
## screen_name avg_likes avg_retweets
## <chr> <dbl> <dbl>
## 1 enlaucab 12.7 5.79
## 2 Unimet 1.62 0.677
## 3 usm_vzla 4.25 1.18
## Veamos lo mismo, pero a nivel de mes
timelines %>%
filter(year(created_at)==2022 & month(created_at)>5) %>%
group_by(year(created_at),month(created_at),screen_name) %>%
summarise(avg_likes=mean(favorite_count,na.rm=T),
avg_retweets=mean(retweet_count, na.rm=T))
## # A tibble: 6 x 5
## # Groups: year(created_at), month(created_at) [2]
## `year(created_at)` `month(created_at)` screen_name avg_likes avg_retweets
## <dbl> <dbl> <chr> <dbl> <dbl>
## 1 2022 6 enlaucab 9.58 5.43
## 2 2022 6 Unimet 1.77 0.752
## 3 2022 6 usm_vzla 13.7 20.9
## 4 2022 7 enlaucab 27.0 7.44
## 5 2022 7 Unimet 1.07 0.387
## 6 2022 7 usm_vzla 5.5 1.58
# Podemos obtener listas de followers con get_followers()
# followers_ucab<-get_followers("enlaucab")
# followers_edn<-get_followers("escueladenada")
## revisemos el listado de followers de la ucab
followers_ucab
## # A tibble: 5,000 x 1
## user_id
## <chr>
## 1 356500474
## 2 4203492083
## 3 1510687080669712387
## 4 1279539269783224321
## 5 717416416350248960
## 6 1123568213919444993
## 7 1502055476657369114
## 8 364002789
## 9 1545180746457010181
## 10 2387046542
## # ... with 4,990 more rows
# Proporcion de seguidores de la UCAB que sige a escuela de nada
nrow(followers_ucab %>%
inner_join(followers_edn, by="user_id"))/nrow(followers_ucab)
## [1] 0.0046
## Podemos complementar la informacion de los usuarios con lookup_users()
# followers_ucab_plus<-lookup_users(followers_ucab$user_id, parse = TRUE, token = NULL)
# followers_edn_plus<-lookup_users(followers_edn$user_id, parse = TRUE, token = NULL)
## Revisemos el listado de followers aumentado de la UCAB
head(followers_ucab_plus)
## # A tibble: 6 x 90
## user_id status_id created_at screen_name text source
## <chr> <chr> <dttm> <chr> <chr> <chr>
## 1 356500474 154303509~ 2022-07-02 00:53:28 eudyziliani "Hermo~ Twitt~
## 2 4203492083 154454347~ 2022-07-06 04:47:14 Holwsito "@Sm0k~ Twitt~
## 3 1510687080669712387 154593570~ 2022-07-10 00:59:29 laage1965 "Want ~ Twitt~
## 4 1279539269783224321 154593231~ 2022-07-10 00:46:00 pdeportiva8~ "Nada ~ Twitt~
## 5 717416416350248960 154593190~ 2022-07-10 00:44:23 Johy_3029 "Dios ~ Twitt~
## 6 1123568213919444993 154591999~ 2022-07-09 23:57:04 AbgLuciaGar~ "Están~ Twitt~
## # ... with 84 more variables: display_text_width <int>,
## # reply_to_status_id <chr>, reply_to_user_id <chr>,
## # reply_to_screen_name <chr>, is_quote <lgl>, is_retweet <lgl>,
## # favorite_count <int>, retweet_count <int>, quote_count <int>,
## # reply_count <int>, hashtags <list>, symbols <list>, urls_url <list>,
## # urls_t.co <list>, urls_expanded_url <list>, media_url <list>,
## # media_t.co <list>, media_expanded_url <list>, media_type <list>, ...
## Vemaos donde estan ubicados estos seguidores
followers_ucab_plus %>%
mutate(lugar=case_when(str_detect(location,"aracas")|
str_detect(location, "Distrito Capital")~"Caracas",
str_detect(location,"zuela") |
str_detect(location,"Vzla") |
str_detect(location,"VENEZUELA")~"Venezuela",
TRUE~location)) %>%
group_by(lugar) %>%
summarise(count=n(),
share=count/nrow(followers_edn_plus)*100) %>%
top_n(20,count) %>%
arrange(desc(count))
## # A tibble: 22 x 3
## lugar count share
## <chr> <int> <dbl>
## 1 "" 3098 62.0
## 2 "Venezuela" 729 14.6
## 3 "Caracas" 531 10.6
## 4 "España" 8 0.16
## 5 "Miami, FL" 8 0.16
## 6 "Buenos Aires, Argentina" 7 0.14
## 7 "Bogotá, D.C., Colombia" 6 0.12
## 8 "Lima, Peru" 6 0.12
## 9 "San Antonio De Los Altos, Vene" 5 0.1
## 10 "Valencia" 5 0.1
## # ... with 12 more rows
## Veamos donde estan ubicados los seguidores de EDN
followers_edn_plus%>%
mutate(lugar=case_when(str_detect(location,"aracas")|
str_detect(location, "Distrito Capital")~"Caracas",
str_detect(location,"zuela") |
str_detect(location,"Vzla") |
str_detect(location,"VENEZUELA")~"Venezuela",
TRUE~location)) %>%
group_by(lugar) %>%
summarise(count=n(),
share=count/nrow(followers_edn_plus)*100) %>%
top_n(20,count) %>%
arrange(desc(count))
## # A tibble: 28 x 3
## lugar count share
## <chr> <int> <dbl>
## 1 "" 3368 67.4
## 2 "Venezuela" 618 12.4
## 3 "Caracas" 228 4.56
## 4 "Santiago, Chile" 27 0.54
## 5 "Bogotá, D.C., Colombia" 16 0.32
## 6 "Lima, Peru" 16 0.32
## 7 "Miami, FL" 16 0.32
## 8 "Chile" 15 0.3
## 9 "Buenos Aires, Argentina" 12 0.24
## 10 "Ciudad Autónoma de Buenos Aire" 12 0.24
## # ... with 18 more rows
# Busquemos tweets con search_tweets
# tweets<-search_tweets("UCAB",n=1000, include_rts = FALSE)
# tipicamente nos interesa conservar el nombre del usuario, el texto, la cantidad de likes y la cantidad de reweets
tweets %>%
select(screen_name, text, favorite_count, retweet_count)
## # A tibble: 1,000 x 4
## screen_name text favorite_count retweet_count
## <chr> <chr> <int> <int>
## 1 The_SoulGreen "WTF una academia de gamers en ~ 0 0
## 2 Jmsn96 "Lo único en lo que nunca se ha~ 1 0
## 3 LuisSaidThat "@sam_nenko @p1edrer0 Reconocer~ 0 0
## 4 marquelisgrecia "\U0001f468\U0001f3fb<U+200D>\U0001f393~ 0 0
## 5 PolitikaUCAB "Conoce la oferta académica vig~ 0 0
## 6 PolitikaUCAB "Conoce la oferta académica vig~ 0 1
## 7 PolitikaUCAB "UCAB y @CreemosAlianzaC\ndicen~ 1 0
## 8 PolitikaUCAB "¿Ya visitaste nuestro canal de~ 0 0
## 9 PolitikaUCAB "Te esperamos en nuestro nuevo ~ 0 0
## 10 SLuisMiguel "@arq_guimaraes En la UCAB ense~ 0 0
## # ... with 990 more rows
Parece qe los teweets de la UCAB se dispararón el 9 de Julio. ¿Alguién tiene idea de que pasó ese día?
# hay una funcion pre-fabricada para visualizar la cantidad de tweets en el tiempo
ts_plot(data = tweets, by= "hour")
El procesamiento de datos de texto requiere conocimiento sobre RegEx (regular expresions), una sintáxis útil para identificar patrones en un texto determinado. Acá les dejo una guía que me pareció muy buena.
# Preprocesamiento de los tweets
## Primero Limpiemos el texto
### Remueve caracteres especiales como /, @, | , etc
### Remueve espacios innecesarios
### Pon tood en minusculas
clean_tweets <- function(x) {
x %>%
# Remove URLs
str_remove_all(" ?(f|ht)(tp)(s?)(://)(.*)[.|/](.*)") %>%
# Remove mentions e.g. "@my_account"
str_remove_all("@[[:alnum:]_]{4,}") %>%
# Remove hashtags
str_remove_all("#[[:alnum:]_]+") %>%
# Remove icons e.g. \U0001f468
# str_remove_all("[^\x01-\x7F]") %>%
# Replace "&" character reference with "and"
str_replace_all("&", "y") %>%
# Remove puntucation, using a standard character class
str_remove_all("[[:punct:]]") %>%
# Remueve otros simbolos como | y /
str_remove_all("/") %>%
str_remove_all("\\|") %>%
# Remove "RT: " from beginning of retweets
str_remove_all("^RT:? ") %>%
# Replace any newline characters with a space
str_replace_all("\\\n", " ") %>%
# Make everything lowercase
str_to_lower() %>%
# Remove any trailing whitespace around the text
str_trim("both")
}
# Podemos discutir si remover o no quotes y retweets, pero no lo haremos
# text_cleaned<-clean_tweets(subset(tweets,is_retweet==F |is_retweet==F)$text)
text_cleaned<-clean_tweets(tweets$text)
## Introduce los tweets en un "cuerpo" de texto, una estructura de datos para el analiss de texto
clean_corpus<-function(texto=text_cleaned,
raiz=TRUE){
### Para el corpus debemos almacenar la data en un objeto source. Si la data esta en vector, usa VectorSource(), si esta en data.frame, usa DataframeSource()
ds <- VectorSource(texto)
### usa la funcion simplecorpus, recuerda especificar el idioma
texto_corpus<-SimpleCorpus(x = ds , control = list(language = "sp"))
## Empieza otra proxima ronda de limpieza
### Remueve los stopwords (i,e. el, las, que, un, por, como, si, este, tambien,etc)
texto_corpus <- tm_map(texto_corpus, removeWords, stopwords("spanish"))
### Aca puedes aprovechar de remover otras palabras que quieras sacar
texto_corpus <- tm_map(texto_corpus, removeWords, c("ucab", "universidad", "venezuela","venezolano","saime"))
if (raiz==TRUE){
### Reduce cada palabra a su forma de raiz (root form)
texto_corpus <- tm_map(texto_corpus, stemDocument,language = "spanish")
}
return(texto_corpus)
}
texto_analisis<-clean_corpus(texto = text_cleaned, raiz=FALSE) #Como el texto es bastante basico no quiero reducir las palabras, pero si quieren vean que pasa cuando usamos raiz=TRUE (Corre esto en la consola: wordStem(c("aprender", "aprendiendo", "aprendió"), "spanish"))
# Descomponé el cuerpo de texto en una matriz donde cada celda es una palabra
create_matrix<-function(texto_corpus=texto_analisis){
## Construye una matriz term-document
texto_corpus_matrix <- TermDocumentMatrix(texto_corpus)
tc_m <- as.matrix(texto_corpus_matrix)
print(tc_m[1:5,1:10])
## Crea un data.frame con la frecuencia en la que aparece cada palabra (usaremos formulas de manipulacion de matrices)
### Primero crea un vector con el total de apariciones de cada palabra (suma de filas)
tc_frecuencia<-rowSums(tc_m)
### Luego crea un data.frame
tc_data <- data.frame(word = names(tc_frecuencia),freq=tc_frecuencia) %>%
arrange(desc(freq))
return(list(data=tc_data,
matrix=texto_corpus_matrix))
}
texto_data<-create_matrix(texto_corpus = texto_analisis)[[1]]# Para visualizaciones
## Docs
## Terms 1 2 3 4 5 6 7 8 9 10
## academia 1 0 0 1 0 0 0 0 0 0
## counters 1 0 0 0 0 0 0 0 0 0
## gamers 1 0 0 1 0 0 0 0 0 0
## jugadera 1 0 0 0 0 0 0 0 0 0
## plena 1 0 0 0 0 0 0 0 0 0
texto_matriz<-create_matrix(texto_corpus = texto_analisis)[[2]]# Para outputs mas analiticos
## Docs
## Terms 1 2 3 4 5 6 7 8 9 10
## academia 1 0 0 1 0 0 0 0 0 0
## counters 1 0 0 0 0 0 0 0 0 0
## gamers 1 0 0 1 0 0 0 0 0 0
## jugadera 1 0 0 0 0 0 0 0 0 0
## plena 1 0 0 0 0 0 0 0 0 0
### revisemos la nueva data
head(texto_data, 5)
## word freq
## academia academia 249
## esports esports 190
## videojuegos videojuegos 56
## deportes deportes 55
## primarias primarias 53
inspect(texto_matriz)
## <<TermDocumentMatrix (terms: 3579, documents: 1000)>>
## Non-/sparse entries: 9459/3569541
## Sparsity : 100%
## Maximal term length: 29
## Weighting : term frequency (tf)
## Sample :
## Docs
## Terms 217 343 559 560 561 562 824 928 929 944
## academia 0 0 0 0 0 0 0 0 0 0
## caracas 0 0 1 1 1 1 0 0 0 0
## centro 0 0 0 0 0 0 0 1 1 0
## deportes 0 0 0 0 0 0 0 0 0 0
## electrónicos 0 0 0 0 0 0 0 0 0 0
## esports 0 0 0 0 0 0 0 0 0 0
## inauguró 0 0 0 0 0 0 0 0 0 0
## primarias 0 1 0 0 0 0 0 0 0 0
## primera 0 0 0 0 0 0 0 0 0 0
## videojuegos 0 0 0 0 0 0 0 0 0 0
# Visualicemos la data
texto_data %>%
top_n(15, freq) %>%
ggplot(aes(x= reorder(word,freq), y=freq))+
geom_col(color="white")+
coord_flip()+
labs(title="Palabras mas comunes en tweets que mencionan `UCAB`",
x="Palabra",
caption = paste0("Muestra aleatoria de 1000 tweets, publicados en ",
date(min(tweets$created_at))))
# Generemos una nube de palabras
set.seed(1234)
wordcloud(words = texto_data$word, freq = texto_data$freq, min.freq = 5,
max.words=100, random.order=FALSE, rot.per=0.40,
colors=brewer.pal(8, "Dark2"))
### Hagamoslo con otro tipo de tweets
# tweets_saime<-search_tweets("SAIME",n=1000, include_rts = FALSE)
#### Anidemos las funciones que creamos antes (eso es lo bueno de haberlas hecho ;D )
texto_data_saime<-create_matrix(texto_corpus =
clean_corpus(texto = clean_tweets(
x = tweets_saime$text),
raiz = FALSE))[[1]]
## Docs
## Terms 1 2 3 4 5 6 7 8 9 10
## llega 1 0 0 0 0 0 0 0 0 0
## tambien 1 0 0 0 0 0 0 0 0 0
## activistas 0 1 0 0 0 0 0 0 0 0
## agrupa 0 1 0 0 0 0 0 0 0 0
## alerta 0 1 0 0 0 0 0 0 0 0
set.seed(1234)
wordcloud(words = texto_data_saime$word,
freq = texto_data_saime$freq, min.freq = 5,
max.words=100, random.order=FALSE, rot.per=0.40,
colors=brewer.pal(8, "Dark2"))
Hemos usado muchas veces la correlación para entender que tán correlacionadas están distintas variables entre si. Esta técnica también puede usarse para ver que palabras se asocian mas frecuentemente a otras en el texto analizado. Echemos un vestazo a las palabras mas asociadas con las palabras “pasaporte” y “meses”.
matriz_data_saime<-create_matrix(clean_corpus(texto = clean_tweets(x = tweets_saime$text), raiz = FALSE))[[2]]
## Docs
## Terms 1 2 3 4 5 6 7 8 9 10
## llega 1 0 0 0 0 0 0 0 0 0
## tambien 1 0 0 0 0 0 0 0 0 0
## activistas 0 1 0 0 0 0 0 0 0 0
## agrupa 0 1 0 0 0 0 0 0 0 0
## alerta 0 1 0 0 0 0 0 0 0 0
findAssocs(matriz_data_saime, terms = c("pasaporte","meses"), corlimit = .35)
## $pasaporte
## agilización antecede anulacion capture consula
## 0.39 0.39 0.39 0.39 0.39
## dactidoscopil desvios ecuperación reseteo vip
## 0.39 0.39 0.39 0.39 0.39
## solicitud prórrogas
## 0.38 0.35
##
## $meses
## aun $500 adelantar demora ponga dura mano trabaje
## 0.44 0.39 0.39 0.39 0.39 0.37 0.36 0.36
## asignan pongan
## 0.36 0.35
La última técnica que veremos en la sección tiene que ver con el analisis de sentimientos. Sucintamente, esta técnica se útiliza para extraer información sobre la connotación del lenguaje en un documento. Se usa recurrentemente en el marketing y en la política.
Referencias: https://programminghistorian.org/es/lecciones/analisis-de-sentimientos-r#paquete-syuzhet
# regular sentiment score using get_sentiment() function and method of your choice
# please note that different methods may have different scales
excluir<-c("venezolanos","país","200$","chile","zulianos")
sentimientos_df <- get_nrc_sentiment( subset(texto_data_saime,
!word %in% excluir)[c(1:900),]$word,
language = "spanish")
# Vistazo a los datos
head(sentimientos_df)
## anger anticipation disgust fear joy sadness surprise trust negative positive
## 1 0 0 0 0 0 0 0 0 0 0
## 2 0 0 0 0 0 0 0 1 0 0
## 3 0 0 0 0 0 0 0 0 0 0
## 4 0 0 0 0 0 0 0 0 0 0
## 5 0 1 0 0 0 0 1 0 1 1
## 6 0 0 0 0 0 0 0 0 0 0
# Visualicemos la proporcion de palabras asociadas a cada sentimiento
sentimientos_df %>%
# Apliquemos pivot longer. Lo recuerdan? es de la primera clase
tidyr::pivot_longer( cols = everything(),
names_to = "emocion",
values_to = "valor") %>%
group_by(emocion) %>%
mutate(share=mean(valor)) %>%
ggplot(aes(x=reorder(emocion,share),y=share))+
geom_col()+
labs(title="Analisis de sentimiento de tweets que mencionan `SAIME`",
x="Emocion",
caption = paste0("Muestra aleatoria de 1000 tweets, publicados en ",
date(min(tweets_saime$created_at))," previamente tokenizados"))
El análisis de redes nos vendrá muy bien para comprender las formas en las que viaja la información entre distintos usuarios de twitter.
Usaremos la aplicación Chipr, desarrollada en Shiny, para extraer una base de datos de la Tiwtter API en un formato idóneo para el análisis de redes (Veamos la app antes de ir al codigo)
# Previamente descarqué una data llamada Guaidó para que la analizaramos hoy. Si alguno tiene una idea genial podemos hacerlo con una base de datos diferente.
# (la data esta en el data.frame tw, que se cargo cuando abri este archivo mas arirba [all_twitter_text_data.RData])
## Inspecciono el data.frame tw
head(tw) # es una base de tweets commun, fijate que tiene casi la misma cantidad de columnas que la data con los tweets de la ucab
## # A tibble: 6 x 88
## user_id status_id created_at screen_name text source
## <chr> <chr> <dttm> <chr> <chr> <chr>
## 1 65649158 154597886~ 2022-07-10 03:50:58 SalvatoreR "#9Jul~ Twitt~
## 2 1051910588 154597883~ 2022-07-10 03:50:51 nancyaiham "@GDLA~ Twitt~
## 3 114831411 154597881~ 2022-07-10 03:50:48 pluk2008 "Yo no~ Twitt~
## 4 1361760039837523977 154597876~ 2022-07-10 03:50:34 MGonzalez20~ "La \"~ Twitt~
## 5 1361760039837523977 154597832~ 2022-07-10 03:48:49 MGonzalez20~ "Mient~ Twitt~
## 6 1453578848 154597873~ 2022-07-10 03:50:28 FDPHC_SO "Guaid~ Twitt~
## # ... with 82 more variables: display_text_width <dbl>,
## # reply_to_status_id <chr>, reply_to_user_id <chr>,
## # reply_to_screen_name <chr>, is_quote <lgl>, is_retweet <lgl>,
## # favorite_count <int>, retweet_count <int>, hashtags <list>, symbols <list>,
## # urls_url <list>, urls_t.co <list>, urls_expanded_url <list>,
## # media_url <list>, media_t.co <list>, media_expanded_url <list>,
## # media_type <list>, ext_media_url <list>, ext_media_t.co <list>, ...
## Armemos una red de Retweets
enlaces<-tw %>%
filter(is_retweet==T) %>%
# la cuenta que hizo el retwwet esta disponible para los retweets,
# la cuenta que dio el fav, esta disponible para los favs, y asi.
select(user_id, retweet_user_id,is_retweet)
## Este paso es critico: Debes armar una base de datos con los nodos. Cuales son los nodos: Todos los id de usuarios que estan en el origen y el destino de los retweets. No pueden faltar nodos que esten en la base de datos de enlaces.
table(unique(enlaces$retweet_user_id) %in% unique(enlaces$retweet_user_id))
##
## TRUE
## 78
nodos<-enlaces %>%
select(id=user_id) %>%
rbind(enlaces %>%
select(id=retweet_user_id) ) %>%
distinct(id) %>%
# tengo todos los id de los usuarios en la red.
# ahora debo adjuntarle los atributos individuales que me interesen
left_join(tw %>%
distinct(id=user_id,screen_name, verified))
## Esta función del paquete igraph transforma cualquier data que tenga origen y destino en columnas 1 y 2 en un objeto graph.
data_red_rt<- igraph::graph_from_data_frame(d = enlaces,
vertices = nodos,
directed = FALSE)
## Inspeccionemos el objeto data_red_rt
data_red_rt
## IGRAPH 723dcdd UN-- 435 429 --
## + attr: name (v/c), screen_name (v/c), verified (v/l), is_retweet (e/l)
## + edges from 723dcdd (vertex names):
## [1] 65649158 --221378668
## [2] 114831411 --79287699
## [3] 1361760039837523977--637145380
## [4] 1361760039837523977--512442219
## [5] 1453578848 --114909288
## [6] 1439796459201781765--79287699
## [7] 1365395059877830659--505731001
## [8] 141654491 --114909288
## + ... omitted several edges
## Manipulacion: Asi se accede a los atributos de los nodos
V(data_red_rt)$screen_name
## [1] "SalvatoreR" "pluk2008" "MGonzalez2021" "FDPHC_SO"
## [5] "GeomilevR" "elrealengo3" "__R2R2__" "MiguelRavelo20"
## [9] "JRJSantaella" "connieestupinan" "CarlosA95769557" "dona_paloma"
## [13] "laloduarte60" "valliente27" "Thays_maure" "GarabatoG"
## [17] "EdwarsKing" "ramirofalconm" "miguelvilchez16" "cootacr3"
## [21] "TuiteroNora" "LuisRobertoBas6" "alfonsove1" "chavezsyn"
## [25] "YanezErvin" "Luisalfonsobae3" "joluso2010" "Montaa84515864"
## [29] "rosadodeserto62" "PeixotoPita" "JULIOVNAVARRO" "nava_jhonattan"
## [33] "aaguilarucv" "edit_luisa" "MirianEMendoza" "rafaelignacioLD"
## [37] "MarioBrea3" "beavis19755" "pcar62" "moreno_alej540"
## [41] "yojan_ayala" "ElDiabl59987144" "FaustoCambio21" "mairmasolp"
## [45] "Elizabeth_lop14" "jkastro18" "grmariae" "DDimadise88"
## [49] "Yajaira75970111" "Carlosurbanejaw" "lfldefender" "EdgarBravoBello"
## [53] "dilcia_tovar" "Nelsonpvzla1" "TheHend7" "froropezaa"
## [57] "hernandobarros3" "Kaleno87" "leonardo_mojica" "carmensa881"
## [61] "ilusionvzla" "WilliamsZaidee" "SofaSua75333153" "JessicaGilP"
## [65] "sylvianorabock" "adolfocaroc" "JOSEAguila52" "papatakisluna"
## [69] "oscarbl" "josecamilohh" "ADorgChacao" "jjvaldezbplus"
## [73] "loidadevalera" "Ctoror" "rivassarai19" "PepeVzlano"
## [77] "zoraida306" "Jacobo35321994" "mangareyes" "padulensis"
## [81] "SecFemeADChacao" "FcomunicMichele" "MZarran" "Kikogerson"
## [85] "jorge49987024" "Carlosa22514170" "SantiagoDickZ" "CsarGonzlez5"
## [89] "edgardhoover" "TriniReyes16" "nuez_zaida" "electricosroya"
## [93] "OscarPaezVe" "veredicto4F" "JapoSegun" "zuindaguiller"
## [97] "ArlynsFuentes" "solminberry" "carlosgermangar" "crismarucha"
## [101] "DuinVzla" "ReinaTo86319109" "VocesDeLaGrita" "KarenGonza2003"
## [105] "luiscerong" "castormacarenio" "VilmaFrancesch4" "ElMorroLecheria"
## [109] "SalgadoEulice" "TERMINE1TOR" "jyyz1973" "JorgeGeorge62"
## [113] "cgla1967" "EdgarGalarraga7" "peterkingindi" "deoli10yacsa"
## [117] "LaPazPrimero20" "LuzBlancaVzla" "VicmarBarbie" "jakousi5000"
## [121] "Iris_victoria" "IsaacMDiaz" "ivanlop33hotmai" "GordonAlejandro"
## [125] "NazarioCanaAnge" "limarcas" "Chubetoar" "OscarArmao"
## [129] "rafaelsalazarc3" "kleorojas" "giulik3" "nelsonmaf"
## [133] "sotoruz" "Capp866" "Lucafon22" "jos66844136"
## [137] "omega30508286" "Abogados10Jesus" "GledysGonzlez1" "donorione"
## [141] "amilcarrock" "AceptaloYya" "bereuban" "reniercj"
## [145] "Gusano2999Gusta" "c7d6b14e0f54466" "argenisjdpvzla" "ALFREDOYSI"
## [149] "Venezuelalibr35" "SheidaJimenez" "RenewEuropa" "GregorioHered12"
## [153] "jamc005" "Francis32710528" "ren_cohones" "MustangWagon"
## [157] "hoswaldosg" "reclamo2021" "marcobonilla30" "55jamp"
## [161] "gaetanoblandini" "ediponte" "beamolero" "13Heledis"
## [165] "Mon_Barrios" "cjgoitia" "Astrid1979kell" "AurelinaCH"
## [169] "milenamata1" "brian70_ve" "jesusnini" "NituPerez"
## [173] "vj13oliveros" "HERNANSALDANAC" "oscaraltuve2" "luiseloi"
## [177] "Raquetu" "adamartinezchie" "ervinoliverosv" "zukyswimwear"
## [181] "ElcideFlaca1953" "yimilcejb" "Edo30792703" "perio_comunic"
## [185] "BelkysMrquez4" "justo_thermo" "Rjcristian18" "QuevedoQ"
## [189] "murillocasanova" "PanchoMoncho" "emmaj_gonzalez" "jotabelandria"
## [193] "velizwilliam64" "GeorgeArtwell" "naizir_mariana" "cortesia_sor"
## [197] "Abuela_Bella_15" "mapyrc" "Elizabe14960065" "netmanven_ve"
## [201] "UNIONyVICTORIA" "alyacar55" "JhonnyArmas7" "lfcr81"
## [205] "mcwated0356" "yuly_perozo" "jcajias" "sekiio"
## [209] "JorgeAl44544833" "Tinkeringhalo2" "dra_mouzo" "idp240664"
## [213] "lizcanorubioH" "BelkysTweets" "scorpio66966" "vzlarenace1"
## [217] "VZLACANDELA" "rivas_anubis" "gguevarapalma" "carmenvriv"
## [221] "RicardoVera1" "belkisro467" "CieloCorazon23" "HctorBello1"
## [225] "GloriaGonzlez8" "Darwiniano" "JimenezMaria48" "ramiromorabello"
## [229] "JuHeCha" "adrianaduque71" "josein123" "EricOndarroa"
## [233] "grabor35" "LiendoLauri" "reginoestevez" "Camiloo410"
## [237] "arduinifm" "Jose2Rivas" "carlosl44679940" "catiraeli"
## [241] "MmVillacis" "eduardoplacerda" "jacardio" "jgvelasquezb"
## [245] "Asciudadania" "unagalaxianueva" "MerlynMata3" "AbisaiAridna"
## [249] "fandango110831" "Jo_castromax" "careduarcam" "marydevies"
## [253] "EnderGo15845772" "CassinelliR" "YusmaryAguilar1" "alexand00426843"
## [257] "DEYSIMMUGUERZAB" "AntonioCPerezH2" "elguin_antoima" "Jorgito49718006"
## [261] "ManzoTairi" "MariaMorillo6" "JosRamiroTorre6" "lunalunera1831"
## [265] "Patriotaccs1" "ivan18hole" "ReguloJLepageL" "susveray28"
## [269] "Prrincon" "LopezMarlyst" "Guayacannnn" "Alpf27"
## [273] "P_e_rla" "Elena_Figue" "rbarrientos_g" "juliocesarh17"
## [277] "weca46" "PetucioS" "dorantesa10" "mangelcc"
## [281] "yolaven" "douglasogonzal1" "juandpahe" "lemancy"
## [285] "jennyca35561303" "ClementeSeplve1" "Benijesus28" "ACamposVPValera"
## [289] "mariaemaggi" "elguillei" "David1329930754" "pfornera"
## [293] "jhon13391" "IngjoacoGONZA" "Ismary28635282" "Raovares63"
## [297] "Samarjos1" "tibisaycarvaja1" "JaimeLa55351235" "gerardbey"
## [301] "Frankribi3" "xmara15" "MarioxiOjeda" "GiuseppeBrai"
## [305] "Ensuret" "PaulDooling" "inversioneslv55" "henry23zulia"
## [309] "rafaelalexis" "JoseAguilarAg13" "Afortunada3" "Leni0410"
## [313] "rodriguezolga71" "Bladimi65148363" "GracielaSkyM" "Arnoldofernan16"
## [317] "WGuevarapico" "rjuzcateguip" "Estheroviedo21" "es12828070"
## [321] "gomezpe" "BryanOrtega_VE" "ANTONIORRAMIRE2" "Norberto_Mende8"
## [325] "rafaelr77921423" "RicardoASaezGo1" "mserrano07" "luchamoral"
## [329] "Richarderojasm" "Marlon_7612" "sllucchese" "EchegarayI"
## [333] "AraguaTamanaco" "norma1502" "enyub" "Mikelmoro"
## [337] "RafaelR43545247" "mervinalvarado3" "ADONAYDUQUE" "robertoriosp"
## [341] "barazartemirian" "avilancaro" "IgnacioVillafu5" "necovery"
## [345] "ifeep" "A33905926" "luisorva" "CarlosLodije"
## [349] "yimyjo" "bellarubiavalk" "gbfenix" "IsabelC26333765"
## [353] "ZoiloFlores1" "guerrero0405" "Vittorioch_" "Ernesto49690273"
## [357] "mallumiquingas" "leoncitcandanga" "Orlando57524372" "lenguafree"
## [361] "DDagobertoGomez" NA "monicacorrales" NA
## [365] NA NA NA NA
## [369] NA NA NA NA
## [373] NA NA "TIBYPlaza" NA
## [377] NA NA NA NA
## [381] NA NA NA NA
## [385] NA NA NA NA
## [389] NA NA NA NA
## [393] NA NA "369Cyrus" NA
## [397] NA "radiocentroec" "luiscoOficial" NA
## [401] NA NA NA NA
## [405] NA NA NA NA
## [409] NA NA NA NA
## [413] NA NA NA NA
## [417] NA NA NA "richarhernandez"
## [421] NA "RCR750" NA NA
## [425] NA "la_patilla" NA "contrapuntovzla"
## [429] NA NA NA NA
## [433] NA NA NA
## Manipulacion, asi se accede a los atributos de los enlaces
E(data_red_rt)$is_retweet
## [1] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [16] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [31] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [46] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [61] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [76] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [91] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [106] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [121] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [136] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [151] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [166] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [181] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [196] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [211] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [226] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [241] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [256] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [271] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [286] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [301] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [316] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [331] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [346] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [361] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [376] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [391] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [406] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [421] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## Primer concepto de red: Densidad: Numero de enlaces/n(1-1)
igraph::edge_density(data_red_rt) # la densidad es bastante baja
## [1] 0.004544732
Hay muchisimas medidas de centralidad, acá la mediremos de la forma mas simple: Cuantos retweets están asociados a cada cuenta
## Calculemos la cantidad de retweets de cada usuario: Degree centrality
hist(igraph::degree(data_red_rt))
V(data_red_rt)$centrality<-igraph::degree(data_red_rt)
# igraph::betweenness(): Te dice el numero de "caminos mas cortos" en los que participa un nodo. Alto betweeness, alta capacidad de conectar a distintas personas.
# igraph::closeness(): Te dice el inverso de la cantidad de pasos promedio al resto de la red. Si en promedio llegas a cualquer nodo en 5 pasos, closeness sera 0.2. Si en promedio te tomas 10 pasos, closeness sera 0.1
## Definamos la comunidad o cluster al que pertenece cada nodo
## Las comunidades son grupos cuya interaccion interna excede a la interaccion externa
community<-igraph::cluster_louvain(data_red_rt)
## Asignamos cada nodo a su comunidad, aca nos aseguramos de que se pueda ver en la red
V(data_red_rt)$community<-community$membership
## El paquete ggraph es el mas conveniente y versatil para visualizar redes (en mi opinion). Fijate que funciona igual, con la salvedad de que los geoms estan distinguidos para nodes y enlaces
ggraph::ggraph(data_red_rt)+
ggraph::geom_node_point(aes(size=centrality, fill=as.factor(community)),
shape=21)+
ggraph::geom_edge_link()+
# sabemos que hay demasiadas comunidades, no pongamos la leyenda en este caso
theme(legend.position = "none")
### Puedo excluir a las comunidades pequenas de forma muy similar a como lo hjaria con un data.frame normal.
sort(table(V(data_red_rt)$community))
##
## 8 9 10 17 18 21 25 26 29 30 31 33 36 39 40 41 42 43 44 45 46 47 1 7 13 20
## 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3
## 22 24 32 12 19 27 34 37 14 16 11 23 6 4 5 35 2 38 3 28 15
## 3 3 3 4 4 4 4 5 7 8 9 13 14 16 16 18 35 35 47 50 81
big_communities<-c(12,28,3,38,2,35)
data_red_rt_small<-delete_vertices(data_red_rt, ! V(data_red_rt)$community %in% big_communities)
ggraph::ggraph(data_red_rt_small)+
ggraph::geom_edge_link()+
ggraph::geom_node_point(aes(size=centrality, fill=as.factor(community)),
shape=21)+
# podemos agregarle texto
ggraph::geom_node_text(aes(filter = centrality>1, label = screen_name ),
family = "serif") +
# ampliamos el rango de variacion del tamano
# scale_size(range = c(1, 8)) +
# le ponemos fondo blanco
theme_graph() +
# sabemos que hay demasiadas comunidades, no pongamos la leyenda en este caso
theme(legend.position = "none")
Bazzi, S., M. Fiszbein, & M. Gebresilasse [2020]; “Frontier Culture: The Roots and Persistence of ‘Rugged Individualism’ in The United States,” Econometrica.
Página web https://programminghistorian.org/es/lecciones/analisis-de-sentimientos-r#paquete-syuzhet
Página web https://johncoene.shinyapps.io/chirp_demo/?_ga=2.30727096.683727930.1657419586-368840250.1623652148
Página web - Cómo acceser a los datos de Twitter (link)
Página web Podemos hacer analisis de sentimiento sobre los tweets (link)